16.4. 函数

Java 和 C 之间的主要区别之一是 C 是一种命令式和过程式语言,而 Java 是一种面向对象语言。在 C 中,程序被组织为一个或多个函数。每个 C 程序都必须至少有一个“main”函数,但通常还有许多其他函数。在 Java 中,程序被组织为一组相互作用的对象。类定义定义对象的状态和方法以及与类关联的静态定义和方法。Java 中没有类定义之外的函数。由单个类组成的 Java 程序没有数据成员,只有“public static”方法函数,这种程序在设计上与 C 程序最为相似。

函数将代码分解为可管理的部分并减少代码重复。函数可能需要零个或多个参数作为输入,并且返回特定类型的单个值。函数声明原型指定了函数的名称、返回类型以及参数列表(所有参数的数量和类型)。函数定义包括调用函数时要执行的代码。C 中的所有函数都必须在调用之前声明。这可以通过声明函数原型或在调用函数之前完整定义该函数来实现:

// function definition format:
// ---------------------------
<return type> <function name> (<parameter list>)
{
    <function body>
}

// parameter list format:
// ---------------------
<type> <param1 name>, <type> <param2 name>, ...,  <type> <last param name>

以下是一个示例函数定义。请注意,注释描述了函数的功能、每个参数的详细信息(其用途和应传递的内容)以及函数返回的内容:

/* This program computes the larger of two
 * values entered by the user.
 */
#include <stdio.h>

/* max: computes the larger of two integer values
 *   x: one integer value
 *   y: the other integer value
 *   returns: the larger of x and y
 */
int max(int x, int y) {
    int bigger;

    bigger = x;
    if (y > x) {
        bigger = y;
    }
    printf("  in max, before return x: %d y: %d\n", x, y);
    return bigger;
}

没有返回值的函数应该指定 void 返回类型。以下是 void 函数的示例:

/* prints out the squares from start to stop
 *   start: the beginning of the range
 *   stop: the end of the range
 */
void print_table(int start, int stop) {
    int i;

    for (i = start; i <= stop; i++) {
        printf("%d\t", i*i);
    }
    printf("\n");
}

与任何支持函数或过程的编程语言一样,函数调用 会调用一个函数,并为特定调用传递特定的参数值。函数通过其名称调用并传递参数,每个对应的函数参数都有一个参数。在 C 语言中,调用函数的方式如下:

// function call format:
// ---------------------
function_name(<argument list>);


// argument list format:
// ---------------------
<argument 1 expression>, <argument 2 expression>, ...,  <last argument expression>

C 函数的参数是按值传递(passed by value)的:每个函数参数都被赋予调用者在函数调用中传递给它的相应参数的 。按值传递语义意味着函数中对参数值的任何更改(即,在函数中为参数分配新值)对调用者来说都是 不可见的

以下是对前面列出的 max 和 print_table 函数的一些示例函数调用:

int val1, val2, result;

val1 = 6;
val2 = 10;

/* to call max, pass in two int values, and because max returns an
   int value, assign its return value to a local variable (result)
 */
result = max(val1, val2);     /* call max with argument values 6 and 10 */
printf("%d\n", result);       /* prints out 10 */

result = max(11, 3);          /* call max with argument values 11 and 3 */
printf("%d\n", result);       /* prints out 11 */

result = max(val1 * 2, val2); /* call max with argument values 12 and 10 */
printf("%d\n", result);       /* prints out 12 */

/* print_table does not return a value, but takes two arguments */
print_table(1, 20);           /* prints a table of values from 1 to 20 */
print_table(val1, val2);      /* prints a table of values from 6 to 10 */

下面是完整程序的另一个示例,它展示了对max函数略有不同的实现的调用,该函数有一个附加语句来更改其参数的值(x = y):

/* max: computes the larger of two int values
 *   x: one value
 *   y: the other value
 *   returns: the larger of x and y
 */
int max(int x, int y) {
    int bigger;

    bigger = x;
    if (y > x) {
        bigger = y;
        // note: changing the parameter x's value here will not
        //       change the value of its corresponding argument
        x = y;
    }
    printf("  in max, before return x: %d y: %d\n", x, y);

    return bigger;
}

/* main: shows a call to max */
int main(void) {
    int a, b, res;

    printf("Enter two integer values: ");
    scanf("%d%d", &a, &b);

    res = max(a, b);
    printf("The larger value of %d and %d is %d\n", a, b, res);

    return 0;
}

以下输出显示了该程序两次运行的结果。请注意两次运行中参数x的值(从max函数内部打印)的差异。具体来说,请注意在第二次运行中更改参数x的值不会影响调用返回后作为参数传递给max的变量:

$ ./a.out
Enter two integer values: 11  7
  in max, before return x: 11 y: 7
The larger value of 11 and 7 is 11

$ ./a.out
Enter two integer values: 13  100
  in max, before return x: 100 y: 100
The larger value of 13 and 100 is 100

因为参数是通过 值传递 给函数的,所以改变其某个参数值的max函数的先前版本的行为与不改变其参数值的max的原始版本的行为完全相同。

16.4.1. 运行栈帧

执行栈 跟踪程序中活动函数的状态。每个函数调用都会创建一个新的栈帧(有时称为激活帧激活记录),其中包含其参数和局部变量值。栈顶的帧是活动帧;它表示当前正在执行的函数激活,并且只有其局部变量和参数在范围内。调用一个函数时,会为其创建一个新的栈帧( 推送 到栈顶),并在新帧中为其局部变量和参数分配空间。函数返回时,其栈帧将从栈中移除(从栈顶弹出),而调用者的栈帧则留在栈顶。

对于前面的示例程序,在 max 执行 return 语句之前的执​​行点,执行堆栈将类似于图 1。回想一下,main 传递给 max 的参数值是通过值传递的,这意味着 max 的参数 xy 被赋予了它们对应参数 ab 的值,这些值来自main 中的调用。尽管 max 函数改变了 x 的值,但这种变化不会影响 maina 的值。

A stack with two frames: main at the bottom, and max on top of it.  Main’s stack frame has three variables, a (11), b (7) and res (undefined at this point).  Max’s stack frame also has three variables, x (11), y (7), and bigger (11).

图 1. 从 max 函数返回之前的执行堆栈内容

以下完整程序包含两个函数,并展示了从 main 函数调用它们的示例。在这个程序中,我们在 main 函数上方声明了 maxprint_table 的函数原型,以便 main 尽管先定义,也可以访问它们。main 函数包含完整程序的高级步骤,首先定义它呼应了程序自上而下的设计。此示例包含注释,描述了程序中对函数和函数调用很重要的部分。您还可以下载并运行 完整程序

/* This file shows examples of defining and calling C functions.
 * It also demonstrates using scanf().
 */

#include <stdio.h>

/* This is an example of a FUNCTION PROTOTYPE.  It declares just the type
 * information for a function (the function's name, return type, and parameter
 * list). A prototype is used when code in main wants to call the function
 * before its full definition appears in the file.
 */
int max(int n1, int n2);

/* A prototype for another function.  void is the return type of a function
 * that does not return a value
 */
void print_table(int start, int stop);

/* All C programs must have a main function.  This function defines what the
 * program does when it begins executing, and it's typically used to organize
 * the big-picture behavior of the program.
 */
int main(void) {
    int x, y, larger;

    printf("This program will operate over two int values.\n");

    printf("Enter the first value: ");
    scanf("%d", &x);

    printf("Enter the second value: ");
    scanf("%d", &y);

    larger = max(x, y);

    printf("The larger of %d and %d is %d\n", x, y, larger);

    print_table(x, larger);

    return 0;
}

/* This is an example of a FUNCTION DEFINITION.  It specifies not only the
 * function name and type, but it also fully defines the code of its body.
 * (Notice, and emulate, the complete function comment!)
 */
/* Computes the max of two integer values.
 *   n1: the first value
 *   n2: the other value
 *   returns: the larger of n1 and n2
 */
int max(int n1, int n2)  {
    int result;

    result = n1;

    if (n2 > n1) {
        result = n2;
    }

    return result;
}

/* prints out the squares from start to stop
 *   start: the beginning of the range
 *   stop: the end of the range
 */
void print_table(int start, int stop) {
    int i;

    for (i = start; i <= stop; i++) {
        printf("%d\t", i*i);
    }

    printf("\n");
}